#ifndef PUDGE_GRFX_TEXT_HPP
#define PUDGE_GRFX_TEXT_HPP

#include <pudge/grfx/mesh.hpp>
#include <pudge/math/vec.hpp>
#include <pudge/grfx/font.hpp>
#include <string>
#include <algorithm>

namespace pudge { namespace grfx {

	/// text_shader class, like sprite shader only for drawing text instead.
	struct text_shader
	{
		uniform foreground;
		uniform background;
		uniform sampler;

		vertex_shader vshader;
		frag_shader fshader;
		shader_program program;

		text_shader(std::string const& vfile, std::string const& ffile)
			:	vshader(vfile, file_tag)
			,	fshader(ffile, file_tag)
		{
			// bind position and tcoord attributes to indices 0 and 1.
			bind_attribute(program, "position");
			bind_attribute(program, "tcoord");

			// bind shaders to program
			bind_shader(program, vshader);
			bind_shader(program, fshader);

			// link program
			link_program(program);

			// fetch uniform information
			foreground = uniform(program, "foreground");
			background = uniform(program, "background");
			sampler = uniform(program, "tex");
		}
	};

	struct text
	{
		typedef float real_type;
		typedef char char_type;
		typedef pudge::math::vec<3,real_type> pen_type;
		typedef pudge::grfx::font font_type;
		typedef std::string string_type;

		struct index_traits
		{
			typedef unsigned long value_type;
			typedef STATIC_DRAW usage_type;
			typedef QUADS poly_type;
		};

		struct position_traits
		{
			typedef pudge::math::vec<3,real_type> value_type;
			typedef STATIC_DRAW usage_type;
		};

		struct tcoord_traits
		{
			typedef pudge::math::vec<2,real_type> value_type;
			typedef STATIC_DRAW usage_type;
		};

		unsigned int max_line_width_;
		unsigned int max_line_width() { return max_line_width_; }
		void max_line_width(const unsigned int i) { max_line_width_ = i; }

		float x_scale_, y_scale_;
		float x_scale() { return x_scale_; }
		void x_scale(const float s) { x_scale_ = s; }
		float y_scale() { return y_scale_; }
		void y_scale(const float s) { y_scale_ = s; }

		font_type & font_;
		font_type & font() { return font_; }

		string_type data_;
		string_type & data() { return data_; }

		pen_type pen_;
		pen_type & pen() { return pen_; }

		text(font_type & f, string_type & d) 
			: 	font_(f)
			,	data_(d)
			,	max_line_width_(512)
			,	x_scale_(1.0f)
			,	y_scale_(1.0f)
			,	pen_(0.0f, 0.0f, 0.0f) {}

		unsigned int get_quad_count()
		{
			static const std::string WS("\n\r\t\v");

			unsigned int count = 0;
			for(string_type::iterator i = data_.begin(); i != data_.end(); ++i)
				if(std::find(WS.begin(),WS.end(),*i) == WS.end())
					++count;
			return count;
		}
		// 32 -> 126
	};

	struct stream_text_
	{
		template<class Text> struct stream_
		{
			Text & text_;
			stream_(Text & t) : text_(t) {}

			template
				<	class IndStream
				,	class PosStream
				,	class TcStream
				,	class StrIter
				,	class Pen
				,	class Index >
				void operator()
				(	IndStream & indices
				,	PosStream & positions
				,	TcStream & tcoords
				,	StrIter & sb
				, 	StrIter & se
				,	Pen & pen
				,	Index & index )
			{
				for(;sb != se; ++sb)
				{
					if(*sb == '\t')
					{	
						continue;
					}
					if(*sb == '\v')
					{
						continue;
					}

					indices << index++ << index++ << index++ << index++;

					const PosStream::value_type::value_type x_scale = text_.x_scale();
					const PosStream::value_type::value_type y_scale = text_.y_scale();

					Text::font_type::glyph & gly = text_.font()[*sb];

					TcStream::value_type tc;
					PosStream::value_type pos(pen);

					pos.x += gly.left * x_scale;
					pos.y += gly.top * y_scale;
					tc.s = gly.s;
					tc.t = gly.t;
					positions << pos;
					tcoords << tc;

					pos.x += gly.width * x_scale;
					tc.s += gly.tex_width;
					positions << pos;
					tcoords << tc;

					pos.y -= gly.height * y_scale;
					tc.t -= gly.tex_height;
					positions << pos;
					tcoords << tc;

					pos.x -= gly.width * x_scale;
					tc.s -= gly.tex_width;
					positions << pos;
					tcoords << tc;

					pen.x += gly.advance * x_scale;
				}
			}

			template<class I> I get_line(I b, I e)
			{
				unsigned int width = 0;

				I last_ws = e;

				for(;b != e;++b)
				{
					const I::value_type v = *b;

					if(v == '\r' || v == '\n') 
						return b;

					if(v == ' ' || v == '\t' || v == '\v')
						last_ws = b;

					width += text_.font()[v].advance;
					if(width >= text_.max_line_width())
					{
						if(last_ws == e)
							return b - 1;
						else
							return last_ws;
					}
				}
				return e;
			}

			template<class C> bool is_ldelim(C & c)
			{
				if(c == '\n' || c == '\r') 
					return true;
				return false;
			}

			template<class IndStream, class PosStream, class TcStream> 
			void operator()(IndStream & indices, PosStream & positions, TcStream & tcoords) 
			{
				typedef Text::string_type Str;

				PosStream::value_type pen(text_.pen());
				Text::string_type & data = text_.data();

				Str::iterator begin = data.begin();
				Str::iterator end = data.end();

				IndStream::value_type index = 0;

				for(;;)
				{
					Str::iterator line = get_line(begin,end);
					(*this)(indices, positions, tcoords, begin, line, pen, index);
					pen.y -= text_.font().bbh * text_.y_scale();
					pen.x = text_.pen().x;

					if(line == end)
						return;
					if(is_ldelim(*line)) 
						begin = ++line;
					else 
						begin = line;
				}
			}
		};
		template<class Text> stream_<Text> operator() (Text & t) const
		{
			return stream_<Text>(t);
		}
	} const stream_text;

	void text_test()
	{
		static font f(std::string("\\Windows\\Fonts\\arial.ttf"),24);

		font::image_type & i = f.image();
		texture2d<font::texture_traits> tex(i.width(), i.height(), i.colors());

		text t(f, std::string("Super Cala Fragilistic\nhello\nAReallyLongWordToWrap Another Line"));
		t.pen().x = -0.7f;
		t.x_scale(1.0f/512.0f);
		t.y_scale(1.0f/512.0f);
		t.max_line_width(256);

		std::string vert_src("../../../src/shaders/text_shader.vert");
		std::string frag_src("../../../src/shaders/text_shader.frag");

		vertex_shader vshad ( vert_src, file_tag );
		frag_shader fshad ( frag_src, file_tag );
		shader_program prog;

		bind_attribute(prog, std::string("position"));
		bind_attribute(prog, std::string("tcoord"));
		bind_shader(prog, vshad);
		bind_shader(prog, fshad);
		link_program(prog);
		use_program(prog);

		// setup texture sampler uniform
		long multi = 0;
		uniform sampler(prog, std::string("tex"));
		set_uniform(sampler,multi);

		// setup foreground/background uniform
		uniform foreground(prog, std::string("foreground"));
		uniform background(prog, std::string("background"));
		set_uniform(foreground, pudge::math::vec<4,float>(0.0f,1.0f,1.0f,1.0f));
		set_uniform(background, pudge::math::vec<4,float>(0.0f,1.0f,0.0f,0.0f));

		const unsigned int cnt = t.get_quad_count() * 4;

		index_attribute<text::index_traits> indices;
		vertex_attribute<text::position_traits> positions;
		vertex_attribute<text::tcoord_traits> tcoords;

		cache_attributes(indices, positions, tcoords, stream_text(t), cnt, cnt);

		// enable alpha blending
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		render_attributes(cnt, indices, positions, tcoords, tex);

		decache_attribute(indices);
		decache_attribute(positions);
		decache_attribute(tcoords);
	}
}}

#endif
